package org.manon.json.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;

import org.manon.json.JSONException;
import org.manon.json.Setter;
import org.manon.json.util.UTF8Util;


/**
 * @author <a href="mailto:jacques.militello@gmail.com">Jacques Militello</a>
 */
public final class UTF8Input extends InputReader {

	public static final byte ACCOLADE_OPEN = 123;
	public static final byte ACCOLADE_END = 125;
	public static final byte DOUBLE_QUOTE = 34;
	public static final byte DOUBLE_POINT = 58;
	public static final byte ARRAY_START = 91;
	public static final byte ARRAY_END = 93;
	public static final byte COMMA = 44;
	
	public static final int BYTE_BUFFER_SIZE = 8192;
	
	private final char[] _charBuffer = new char[8192];
	private final byte[] _buffer = new byte[BYTE_BUFFER_SIZE];
	private int _position;
	private int _length;

	public void reset() {
		_position = 0;
	}

	
	/**
	 * @see org.manon.json.io.InputReader#setInputStream(java.io.InputStream)
	 */
	@Override
	public void setInputStream(InputStream inputStream) throws IOException {
		super.setInputStream(inputStream);
		readBuffer();
	}


	public final void readBuffer() throws IOException{
		_length = _inputStream.read(_buffer);
		_position = 0;
	}
	
	protected void incrementPosition() throws IOException {
		_position++;
		if (_position == _length) {
			if (_length < BYTE_BUFFER_SIZE) {
				_length = -1;
				return;
			}
			readBuffer();
		}
	}
	
	private void findNextAllocadeOpen() throws IOException {
		while (_buffer[_position] != ACCOLADE_OPEN) {
			incrementPosition();
			if (_length == -1)  {
				throw new JSONException("End of buffer ?!?");
			}
		}
	}
	
	private boolean next() throws IOException {
		incrementPosition();
		
		if (_length == -1) {
			return false;
		}
		
		if (_buffer[_position] == COMMA) {
			incrementPosition();
			return true;
		}
		if (_buffer[_position] == ACCOLADE_END) {
			return false;
		}
		
		return true;
	}
	
	public <T> T read(Deserializer<T> deserializer) throws IOException {
		// find '{'
		findNextAllocadeOpen();
		
		// next 
		incrementPosition();

		final T pojo;
				
		if (deserializer.pojoHasSubClass()) {
			String disc = readFirstValue();
			next();
			deserializer = deserializer.getSubDeserializer(disc);
		} 
		pojo = deserializer.newPojoInstance();
		
		do  {
			readValue(deserializer,pojo,readStringValue());
		} while(next());
		
		
//		while (next()) {
//			readValue(deserializer,pojo,readStringValue());
//			
//			incrementPosition();
//			
//			if (_length == -1) {
//				break;
//			}
//			
//			if (_buffer[_position] == COMMA) {
//				incrementPosition();
//				continue;
//			}
//			if (_buffer[_position] == ACCOLADE_END) {
//				break;
//			}
//		
//		}			
		return pojo;
	}
	
	private String readFirstValue() throws IOException {
		
		readStringValue();
		
		incrementPosition();
		
		if (_buffer[_position] != DOUBLE_POINT) {
			throw new JSONException("not find [:], it's ["+(char)_buffer[_position]+"] -> " + _position);
		}

		incrementPosition();
		
		return readStringValue();
		
	}
	
	private <T> void readValue(Deserializer<T> deserializer,T pojo,String key) throws IOException {
	
		incrementPosition();
		
		if (_buffer[_position] != DOUBLE_POINT) {
			throw new JSONException("not find [:], it's ["+(char)_buffer[_position]+"] -> " + _position);
		}

		incrementPosition();
		
		final Setter<T,Object> setter = deserializer.getSetter(key);
		
		if (setter == null) {
			throw new JSONException("No Setter found for property [" + key + "] in pojo [" + pojo + "]");
	}
		
		switch (_buffer[_position]) {
		case DOUBLE_QUOTE:
			setter.set(pojo,readStringValue());
			return;
		case ARRAY_START:
		case ACCOLADE_OPEN:
			setter.set(pojo, this);
			return;
		}

		//it's a number;
		setter.set(pojo,readNumberValue());
	}

	
	public <T> void readArray(Collection<T> collection, Deserializer<T> deserializer) throws IOException {
		final byte[] buffer = _buffer;
		
		while ( (buffer[_position]) != ARRAY_END) {
			incrementPosition();
			
			collection.add(read(deserializer));
			
			if (buffer[_position] != ACCOLADE_END) {
				throw new JSONException("");
			}

			incrementPosition();
			
			if (buffer[_position] == COMMA) {
				continue;
			}
		}
	}
	
	
	/** (non-Javadoc)
	 * @see org.manon.json.io.InputReader#readMap(java.util.Map, org.manon.json.io.Converter, org.manon.json.io.Deserializer)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void readMap(@SuppressWarnings("rawtypes") Map map, Converter k, @SuppressWarnings("rawtypes") Deserializer deserializer) throws IOException {
		
		final byte[] buffer = _buffer;
		
		String key = null;
		incrementPosition();
		
		while ( (buffer[_position]) != ACCOLADE_END) {
			
			key = readStringValue();
			incrementPosition();
			if (buffer[_position] != DOUBLE_POINT) {
				throw new JSONException("not find [:], it's ["+(char)_buffer[_position]+"] -> " + _position);
			}

			incrementPosition();
			
			map.put(key,deserializer.deserialize(this));
			
			incrementPosition();
			if (buffer[_position] == COMMA) {
				incrementPosition();
				continue;
			}
			
		}
		
		
	}

	/**
	 * @see org.manon.json.io.InputReader#readMap(java.util.Map, org.manon.json.io.Converter, org.manon.json.io.Converter)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void readMap(@SuppressWarnings("rawtypes") Map map, Converter k, Converter v) throws IOException {
		
		final byte[] buffer = _buffer;
		
		String key = null;
		incrementPosition();
		
		while ( (buffer[_position]) != ACCOLADE_END) {
			
			key = readStringValue();
			incrementPosition();
			if (buffer[_position] != DOUBLE_POINT) {
				throw new JSONException("not find [:], it's ["+(char)_buffer[_position]+"] -> " + _position);
			}

			incrementPosition();
			
			if (v.isNumber()) {
				map.put(k.convert(key), v.convert(readNumberValue()));
			} else {
				map.put(k.convert(key), v.convert(readStringValue()));
			}
			
			incrementPosition();
			if (buffer[_position] == COMMA) {
				incrementPosition();
				continue;
			}
			
		}
	}


	private String readNumberValue() throws IOException {
		_charBuffer[0] = (char)_buffer[_position];
		int charPos = 1;
		byte b;
		incrementPosition();
		while ( (b=_buffer[_position]) != COMMA && b != ACCOLADE_END) {
			_charBuffer[charPos++] = (char)b;
			incrementPosition();
		}
		return new String(_charBuffer,0,charPos);
	}


	private String readStringValue() throws IOException {
		int charPos = 0;
		byte b;
		incrementPosition();
		while ( (b=_buffer[_position]) != DOUBLE_QUOTE) {
			if (b>0) {
				_charBuffer[charPos++] = (char)b;
			} else if ((b & 0xE0) == 0xC0) {
				incrementPosition();
				_charBuffer[charPos++] = (char)UTF8Util.decode(b, _buffer[_position]);
			}
			incrementPosition();
		}
		return new String(_charBuffer,0,charPos);
	}

	private void findDoubleQuote() throws IOException {
		while (_buffer[_position] != DOUBLE_QUOTE) {
			_position++;
		}
	}
	
}